﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Ninject;
using Microsoft.Services.NuGet;
using Microsoft.Services.NuGet.Server.DataServices;
using System.IO;

namespace Microsoft.Services.NuGet.Server.Infrastructure
{
    public class BuildPackageRepository
        : IServerPackageRepository 
    {
        private readonly Dictionary<string, PackageCacheEntry> _packageCache = new Dictionary<string, PackageCacheEntry>(StringComparer.OrdinalIgnoreCase);

        private readonly IDictionary<IPackage, DerivedPackageData> _derivedDataLookup = new Dictionary<IPackage, DerivedPackageData>();

        private readonly Uri _teamProjectCollectionUrl;

        private readonly BuildPackageStore _store;

        public BuildPackageRepository(string teamProjectCollectionUrl)
            : this(new Uri(teamProjectCollectionUrl, UriKind.Absolute)) 
        { }

        public BuildPackageRepository(Uri teamProjectCollectionUrl)
        {
            _teamProjectCollectionUrl = teamProjectCollectionUrl;

            _store = new BuildPackageStore(_teamProjectCollectionUrl);
        }

        [Inject]
        public IHashProvider HashProvider 
        { get; set; }

        public IQueryable<Package> GetPackagesWithDerivedData() 
        {
            return from package in this.GetPackages() select new Package(package, _derivedDataLookup[package]);
        }

        public IQueryable<IPackage> GetPackages()
        {
            return GetPackages(OpenPackage).AsSafeQueryable();
        }

        internal IEnumerable<IPackage> GetPackages(Func<string, IPackage> openPackage)
        {
            foreach (string packageUri in _store.Packages)
            {
                PackageCacheEntry cacheEntry;

                string path = _store.GetFullPath(packageUri);
                DateTimeOffset lastModified = _store.GetLastModified(packageUri);

                // If we never cached this file or we did and it's current last modified time is newer
                // create a new entry
                
                if (!_packageCache.TryGetValue(path, out cacheEntry) ||
                    (cacheEntry != null && lastModified > cacheEntry.LastModifiedTime))
                {
                    // We need to do this so we capture the correct loop variable
                    string packagePath = path;

                    // Create the package
                    IPackage package = openPackage(packagePath);

                    // create a cache entry with the last modified time
                    cacheEntry = new PackageCacheEntry(package, lastModified);

                    // Store the entry
                    _packageCache[packagePath] = cacheEntry;
                }

                yield return cacheEntry.Package;
            }
        }

        protected IPackage OpenPackage(string path) 
        {
            IPackage package = new ZipPackage(path);

            _derivedDataLookup[package] = CalculateDerivedData(path);
            
            return package;
        }

        private DerivedPackageData CalculateDerivedData(string path) 
        {
            FileInfo file = new FileInfo(path);

            if (file.Exists)
            {
                byte[] fileBytes = File.ReadAllBytes(path);

                return new DerivedPackageData
                {
                    PackageSize = fileBytes.Length,
                    PackageHash = Convert.ToBase64String(HashProvider.CalculateHash(fileBytes)),
                    LastUpdated = file.LastWriteTimeUtc,
                    Created = file.CreationTimeUtc
                };
            }

            return null;
        }


        public void AddPackage(IPackage package)
        {
            throw new NotImplementedException();
        }

        public void RemovePackage(IPackage package)
        {
            throw new NotImplementedException();
        }

        private class PackageCacheEntry
        {
            public PackageCacheEntry(IPackage package, DateTimeOffset lastModifiedTime)
            {
                Package = package;
                LastModifiedTime = lastModifiedTime;
            }

            public IPackage Package { get; private set; }
            public DateTimeOffset LastModifiedTime { get; private set; }
        }
    }
}
